﻿#region License
/* ---------------------------------------------------------------- *
 *
 * This file is part of the Xcoordination Application Space
 * ("XcoAppSpace") http://www.xcoordination.com
 *
 * Copyright (C) 2009 Xcoordination http://www.xcoordination.com
 *
 * XcoAppSpace is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General
 * Public License as published by the Free Software Foundation.
 *
 * XcoAppSpace is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://www.gnu.org/licenses/
 * or write to the Free Software Foundation, Inc., 51 Franklin
 * Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ---------------------------------------------------------------- */
#endregion

using System;
using System.Text;
using System.IO;
using XcoAppSpaces.Contracts.Logging;
using XcoAppSpaces.Contracts.Transport;
using System.Diagnostics;

namespace XcoAppSpaces.Transport.MSMQ
{
    ///<summary>
    /// Helperclass for XcoMessage convert for transfer
    ///</summary>
    internal class TransferHelper
    {
        private static readonly int IntByteLength = BitConverter.GetBytes(1).Length;

        private readonly IXcoLogger _log;

        ///<summary>
        /// Helper class for XcoMessages converts
        ///</summary>
        public TransferHelper()
        {
            _log = XcoLoggerFactory.CreateInstance(GetType());
        }


        /// <summary>
        /// Converts the XcoMessage to a byte[]
        /// </summary>
        /// <param name="msg">XcoMessage to convert.</param>
        /// <param name="remoteAddress">The address to which the message should be sent.</param>
        /// <returns>byte[] instance of the XcoMessage</returns>
        public byte[] Convert(XcoMessage msg, string remoteAddress)
        {
            try
            {
                int addrLen = 0;
                int contentInfoLen = 0;
                int contextLen = msg.CausalityContext != null ? msg.CausalityContext.Length : 0;
                int contentLen = msg.Content != null ? msg.Content.Length : 0;
                byte[] addr = null;
				if (!string.IsNullOrEmpty(remoteAddress))
                {
					addr = Encoding.UTF8.GetBytes(remoteAddress);
                    addrLen = addr.Length;
                }
                byte[] contentInfo = null;
                if (!string.IsNullOrEmpty(msg.ContentInfo))
                {
                    contentInfo = Encoding.UTF8.GetBytes(msg.ContentInfo);
                    contentInfoLen = contentInfo.Length;
                }

                var memStream = new MemoryStream();
                byte[] len = BitConverter.GetBytes(addrLen + contextLen + contentLen + contentInfoLen + 4 * IntByteLength);
                memStream.Write(len, 0, len.Length);
                len = BitConverter.GetBytes(addrLen);
                memStream.Write(len, 0, len.Length);
                len = BitConverter.GetBytes(contentInfoLen);
                memStream.Write(len, 0, len.Length);
                len = BitConverter.GetBytes(contextLen);
                memStream.Write(len, 0, len.Length);
                len = BitConverter.GetBytes(contentLen);
                memStream.Write(len, 0, len.Length);
                if (addr != null && addr.Length > 0)
                {
                    memStream.Write(addr, 0, addr.Length);
                }
                if (contentInfo != null && contentInfo.Length > 0)
                {
                    memStream.Write(contentInfo, 0, contentInfo.Length);
                }
                if (msg.CausalityContext != null && msg.CausalityContext.Length > 0)
                {
                    memStream.Write(msg.CausalityContext, 0, msg.CausalityContext.Length);
                }
                if (msg.Content != null && msg.Content.Length > 0)
                {
                    memStream.Write(msg.Content, 0, msg.Content.Length);
                }
                return memStream.ToArray();
            }
            catch (Exception err)
            {
                _log.Error("Error converting XcoMessage to byte[]", err);
                return null;
            }
        }

        /// <summary>
        /// Convert byte[] to XcoMessage
        /// </summary>
        /// <param name="stream">read XcoMessage from stream</param>
        /// <param name="remoteAddress">The address where the message came from.</param>
        /// <returns>new XcoMessage instance</returns>
        public XcoMessage Convert(BinaryReader stream, out string remoteAddress)
        {
        	remoteAddress = null;
            try
            {
                var data = new byte[IntByteLength];
                stream.Read(data, 0, data.Length);
                int gesamtLen = BitConverter.ToInt32(data, 0);

                data = new byte[IntByteLength];
                stream.Read(data, 0, data.Length);
                int addrLen = BitConverter.ToInt32(data, 0);

                data = new byte[IntByteLength];
                stream.Read(data, 0, data.Length);
                int contenInfoLen = BitConverter.ToInt32(data, 0);

                data = new byte[IntByteLength];
                stream.Read(data, 0, data.Length);
                int contextLen = BitConverter.ToInt32(data, 0);

                data = new byte[IntByteLength];
                stream.Read(data, 0, data.Length);
                int contentLen = BitConverter.ToInt32(data, 0);

                Debug.Assert(gesamtLen == addrLen + contextLen + contentLen + contenInfoLen + 4 * IntByteLength, "Length error");

                string addrStr = null;
                string contentInfoStr = null;
                byte[] addr;
                int readLen = ReadData(stream, addrLen, out addr);
                if (readLen != addrLen)
                {
                    return null;
                }
                if (readLen > 0)
                {
                    addrStr = Encoding.UTF8.GetString(addr, 0, addr.Length);
                }
                byte[] contenInfo;
                readLen = ReadData(stream, contenInfoLen, out contenInfo);
                if (readLen != contenInfoLen)
                {
                    return null;
                }
                if (readLen > 0)
                {
                    contentInfoStr = Encoding.UTF8.GetString(contenInfo, 0, contenInfo.Length);
                }
                byte[] context;
                readLen = ReadData(stream, contextLen, out context);
                if (readLen != contextLen)
                {
                    return null;
                }
                byte[] content;
                readLen = ReadData(stream, contentLen, out content);
                if (readLen != contentLen)
                {
                    return null;
                }

            	remoteAddress = addrStr;
                return new XcoMessage
                {
                    ContentInfo = contentInfoStr,
                    CausalityContext = context,
                    Content = content
                };

            }
            catch (Exception err)
            {
                _log.Error("Error converting stream to XcoMessage. Error:", err);

            }
            return null;
        }

        /// <summary>
        /// Read data from stream
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="gesamtLen"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        private static int ReadData(BinaryReader stream, int gesamtLen, out  byte[] content)
        {
            if (gesamtLen <= 0)
            {
                content = null;
                return 0;
            }
            content = new byte[gesamtLen];
            int lenToRead = gesamtLen;

            int readCount = 0;
            while (true)
            {
                int readLen = stream.Read(content, readCount, lenToRead);
                readCount += readLen;
                if (readCount >= gesamtLen)
                {
                    break;
                }
                lenToRead -= readLen;
            }
            return readCount;
        }
    }
}
